/*
 *  QUESTIONS:
 *  1. can't read whole header/pixel buffer will be considered as IO error or as invalid image?
 *
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* All bitmap coordinates are bottom-up, left-to-right */
typedef struct {
	int bottom;
	int top;
	int left;
	int right;
} blur_rect;

#pragma pack(1)
typedef struct
{
	unsigned char b;
	unsigned char g;
	unsigned char r;
}RGB24Pix;

/* Windows 3.x bitmap file header */
typedef struct {
	char         filetype[2];	/* magic - always 'B' 'M' */
	unsigned int filesize;
	short        reserved1;
	short        reserved2;
	unsigned int dataoffset;    /* offset in bytes to actual bitmap data */
} file_header;

/* Windows 3.x bitmap full header, including file header */
typedef struct {
	file_header  fileheader;
	unsigned int headersize;
	int          width;
	int          height;
	short        planes;
	short        bitsperpixel;  /* we only support the value 24 here */
	unsigned int compression;   /* we do not support compression */
	unsigned int bitmapsize;
	int          horizontalres;
	int          verticalres;
	unsigned int numcolors;
	unsigned int importantcolors;
} bitmap_header;

#pragma pack()

typedef enum {
	SUCCESS 						=	0,
	IO_ERROR 						= 	1,
	INVALID_INPUT_IMAGE	= 2,
	MEM_ALLOC_ERROR		= 3,
}Status;

typedef enum
{
	false = 0,
	true = 1,
}bool;

bitmap_header gInputHeader;
char* gInputFileName = NULL;
int gRowByteSize = 0;

Status readBuffer(void* buffer,size_t elSize,size_t elCount,FILE* fd);
Status writeBuffer(void* buffer,size_t elSize,size_t elCount,FILE* fd);

//reads the bitmap header from file
Status getBitMapHeader(FILE * fd)
{
	Status res = readBuffer(&gInputHeader,sizeof(bitmap_header),1,fd);
	//size_t s = fread(&gInputHeader,sizeof(bitmap_header),1,fd);
	gRowByteSize = gInputHeader.bitmapsize/gInputHeader.height;
	return res;

}

//wirte buffer to file
Status writeBuffer(void* buffer,size_t elSize,size_t elCount,FILE* fd)
{
	size_t totalBytes = elSize*elCount;
	while (totalBytes > 0)
	{
		size_t res = fwrite(buffer,1,totalBytes,fd);
		if (res == 0)
		{
			return IO_ERROR;
		}
		totalBytes-=res;
		buffer+=res;


	}

	return SUCCESS;
}
//write output result - original header + pBuffer bitmap buffer to file pOutput
Status writeOutput(void* pBuffer,char* pOutput)
{
	FILE* pOutFd = fopen(pOutput,"wb");
	Status status = IO_ERROR;

	do
	{
		if (!pOutFd)
		{
			break;
		}

		status = writeBuffer(&gInputHeader,sizeof(bitmap_header),1,pOutFd);

		if (status != SUCCESS)
		{
			break;
		}

		if (fseek(pOutFd,gInputHeader.fileheader.dataoffset,SEEK_SET) == -1)
		{
			status = IO_ERROR;
			break;
		}

		status = writeBuffer(pBuffer,1,gInputHeader.bitmapsize,pOutFd);

		if (status != SUCCESS)
		{
			break;
		}

	}while(false);

	if (pOutFd)
	{
		fclose(pOutFd);
	}

	return status;
}

//verify that bitmap is valid
bool verifyImageHeader()
{
	if (strncmp("BM",gInputHeader.fileheader.filetype,2)!=0)
	{
		return false;
	}

	if (gInputHeader.bitsperpixel != 24)
	{
		return false;
	}

	if (gInputHeader.compression)
	{
		return false;
	}

	return true;
}

//checks whether a given rectangle is out of the image
bool isRectOutOfImageBounds(blur_rect rect)

{
	if (rect.bottom < 0 || rect.top >= gInputHeader.height || rect.left < 0 || rect.right >= gInputHeader.width)
	{
		return true;
	}

	return false;
}

//reads buffer from a given file
Status readBuffer(void* buffer,size_t elSize,size_t elCount,FILE* fd)
{
	size_t totalBytes = elSize*elCount;

	while (totalBytes > 0)
	{
		size_t res = fread(buffer,1,totalBytes,fd);
		if (res == 0)
		{
			return INVALID_INPUT_IMAGE;
		}
		totalBytes-=res;
		buffer+=res;
	}

	return SUCCESS;
}

//reads the pixels buffer from a bmp file
Status getImagePixelsBuffer(FILE* pFd,void** ppBuffer)
{

	*ppBuffer = NULL;

	if (fseek(pFd,gInputHeader.fileheader.dataoffset,SEEK_SET) == -1)
	{
		return INVALID_INPUT_IMAGE;
	}

	*ppBuffer = malloc(gInputHeader.bitmapsize);


	if (!*ppBuffer)
	{
		return MEM_ALLOC_ERROR;
	}

	Status status = readBuffer(*ppBuffer,1,gInputHeader.bitmapsize,pFd);

	return status;
}

//get pixel (w,h) from a bitmap buffer
RGB24Pix getPixel(int w,int h,void* pBitmap)
{
	return *(RGB24Pix*)(pBitmap + (h*gRowByteSize + w*sizeof(RGB24Pix)));
}

//set pixel (w,h) of a given bitmap bufefr
void setPixel(int w,int h,RGB24Pix pix,void* pBitmap)
{
	*(RGB24Pix*)(pBitmap + (h*gRowByteSize + w*sizeof(RGB24Pix))) = pix;
}

//checks whether a given (w,h) pixel os out of bounds
bool isPixelOutOfImageBounds(int w,int h)
{
	return (w < 0 || w >= gInputHeader.width || h < 0 || h >= gInputHeader.height);
}

//add given pixel to r,g,b counters
void addPixelToCounters(RGB24Pix src,unsigned int *r,unsigned int *g, unsigned int *b)
{
	*b+=src.b;
	*g+=src.g;
	*r+=src. r;
}

//blure given pixel
void blurePixel(int width,int height,void* pInputBitmap,void* pOutputBitmap)
{
	int w,h;
	RGB24Pix origPix = getPixel(width,height,pInputBitmap);
	RGB24Pix newPix = {0};


	unsigned int r=0,g=0,b=0;

	//iterate over it's 8 neigbours
	for(w = width-1 ; w <= width+1 ; ++w)
	{
		for(h = height-1 ; h <= height+1 ; ++h)
		{
			if (!isPixelOutOfImageBounds(w,h))
			{
				addPixelToCounters(getPixel(w,h,pInputBitmap),&r,&g,&b);
			}
			else
			{
				addPixelToCounters(origPix,&r,&g,&b);
			}
		}
	}

	newPix.r = r/9;
	newPix.g = g/9;
	newPix.b = b/9;

	setPixel(width,height,newPix,pOutputBitmap);



}

//blurs given rectangles' pixels
void blurRectangle(blur_rect rect, void* pInputBitmap,void* pOutputBitmap)
{
	int w,h;
	for(w = rect.left ; w <= rect.right ; ++ w)
	{
		for(h = rect.bottom ; h <= rect.top ; ++h)
		{
			blurePixel(w,h,pInputBitmap,pOutputBitmap);
		}
	}
}

//perform blur on a given image with set of rectangles
void performBlur(blur_rect* pRects,	int count, int iterations, void* pInputBitmap,void* pOutputBitmap)
{
	int i = 0 ,j = 0;

	for(i = 0 ; i < iterations ; ++i)
	{
		for(j = 0 ; j < count ; ++j)
		{
			if (isRectOutOfImageBounds(pRects[j]))
			{
				if (i==0)
				{
					fprintf(stderr, "Rectangle %d %d %d %d exceeds image bounds for '%s'. Skipped.\n",pRects[j].bottom,pRects[j].top,pRects[j].left,pRects[j].right,gInputFileName);
				}
			}
			else
			{
				blurRectangle(pRects[j],pInputBitmap,pOutputBitmap);
			}
		}

		//if this is not the last iteration, use the blurred image as input, and blur it again
		if (i+1 < iterations)
		{
			void* pTemp = pOutputBitmap;
			pOutputBitmap = pInputBitmap;
			pInputBitmap = pTemp;
		}
	}

	if (iterations % 2 == 0)
	{
		memcpy(pInputBitmap,pOutputBitmap,gInputHeader.bitmapsize);
	}
}

//get file name from a given path
char* fileFromPath (char *pathname)
{
	char *lastSlash = NULL;

	lastSlash = strrchr (pathname, '/');

	if (lastSlash == NULL)
	{
		return pathname;
	}
	else
	{
		return lastSlash+1;
	}
}
/*
 * Blurs the specified areas in the provided bitmap.
 *
 * 'input' must point to an existing BMP file in Windows 3.x format
 * with 24 bits per pixel.
 * 'output' must be the name of a writeable existing/non-existing file.
 * 'rects' is a pointer to an array of rectangles to blur.
 * 'count' is the number of elements in that array.
 * 'blur_repetitions' is the number of times to repeat the blur operation
 * on each rectangle.
 *
 * This function assumes that the bitmap will fit into memory.
 * This function does not check for overlapping rectangles. (In fact,
 * nothing terrible will happen if they *are* overlapping, but we do
 * not allow it just for kicks.)
 *
 * Return values:
 *		0 if everything was OK
 *		1 if there was an I/O-related error
 *		2 if the bitmap is not in the expected format
 *		3 if there was an error allocating memory
 */

int bitmapblur(
	char* input,
	char* output,
	blur_rect* rects,
	int count,
	int blur_repetitions)
{

	FILE* pInputFd = NULL;
	Status status = SUCCESS;

	void *pInputBitmap = NULL,*pOutputBitmap = NULL;

	memset(&gInputHeader,0,sizeof(bitmap_header));

	gInputFileName = input;//fileFromPath(input);

	do
	{
		pInputFd = fopen(input,"rb");

		if (!pInputFd)
		{
			status = IO_ERROR;
			break;
		}

		status = getBitMapHeader(pInputFd);

		if (status != SUCCESS)
		{
			break;
		}

		if (verifyImageHeader() == false)
		{
			status = INVALID_INPUT_IMAGE;
			break;
		}

		status = getImagePixelsBuffer(pInputFd,&pInputBitmap);

		if (status != SUCCESS)
		{
			break;
		}

		pOutputBitmap = malloc(gInputHeader.bitmapsize);

		if (!pOutputBitmap)
		{
			return MEM_ALLOC_ERROR;
		}

		memcpy(pOutputBitmap,pInputBitmap,gInputHeader.bitmapsize);

		performBlur(rects,count,blur_repetitions,pInputBitmap,pOutputBitmap);

		status = writeOutput(pOutputBitmap,output);

	}while(0);

	if (pInputFd)
	{
		fclose(pInputFd);
	}

	if (pInputBitmap)
	{
		free(pInputBitmap);
	}

	if (pOutputBitmap)
	{
		free(pOutputBitmap);
	}

	return status;



}


